%{

#include <iostream>
#include <sstream>
#include "expression.hpp"
#include "literal.hpp"
#include "symboltable.hpp"
#include "quad.hpp"
#include "statement.hpp"
#include "callindex.hpp"
#include "variable.hpp"

extern int  yylex( void );
extern void yyerror( const char* errorString );
extern int yylineno;

SymbolTable g_symTable;

typedef std::vector< std::string > IdList;

std::string g_currentArrayType;
int         g_currentArrayStart = 0;
int         g_currentArrayEnd = 0;
int         g_nextline = 0;
std::string g_currentIdentifier;
std::string g_currentCall;
std::string g_currentCallIndex;
int         g_numParams;
Variable    g_current;

QuadArray   g_quadArray;

IdList g_idList;

unsigned int tempCounter = 0;

std::string newTemp()
{
  std::stringstream temp;
  temp << "__temp" << tempCounter++;
  return temp.str();
}

void emit( const std::string& operation,
               const std::string& dst = "",
               const std::string& opA = "",
               const std::string& opB = "" )
{
//  std::cout << "(" << g_nextline++ << ") " << operation << " " << dst << " " << opA << " " << opB << std::endl;
  g_nextline++;
  g_quadArray.push_back( Quad( operation, dst, opA, opB ) );
}

int newOffset( const std::string& type, unsigned int numElements = 1 )
{
  static int offset = 0;
  if( type == "boolean" )
    return offset += 1 * numElements;
  else if( type == "integer" )
    return offset += 2 * numElements;
  else if( type == "char" )
    return offset += 1 * numElements;
  else
    { std::cerr << "Invalid newOffset() call: " << type << std::endl; return 0; }
}

std::string relopToOp( const std::string& relOp )
{
  if( relOp == ">" ) return "jgt";
  else if( relOp == "<" ) return "jlt";
  else if( relOp == "=" ) return "jeq";
  else
    std::cerr << "Invalid relopToOp() call: " << relOp << std::endl;
  return "ERROR";
}

void backpatch( IndexArray& indexArray, const std::string& label )
{
  for( IndexArray::iterator i = indexArray.begin(); i != indexArray.end(); ++i )
  {
    assert( g_quadArray.size() > *i );
    std::clog << "Backpatching line: " << *i << " label " << label << std::endl;
    g_quadArray[*i].Patch( label );
  }
}

%}

%token PROGRAM
%token BEG END
%token TYPE
%token PROCEDURE
%token VALUE ARRAY
%token NOT
%token WHILE DO ENDWHILE
%token IF THEN ELSE ENDIF
%token READ WRITE
%token ASSIGNOP
%token <character> RELOP
%token INT_LITERAL CHAR_LITERAL BOOL_LITERAL
%token IDENTIFIER

%union {
  Expression* exp;      // For expressions.
  Statement*  stat;     // For statements.
  int       integer;    // INT_LITERAL tokens
  char      character;  // CHAR_LITERAL tokens
  bool      boolean;    // BOOL_LITERAL tokens
  char      relOp;      // RELOP tokens
  std::string* identifier;
  std::string* type;
  Literal* literal;
  int       targetForBackpatch;
  CallIndex* callIndex;
  Variable* variable;
}

%type <stat> command command_if command_while command_list m1;
%type <targetForBackpatch> m0;
%type <type> variable2
%type <type> TYPE var_decl
%type <identifier> identifier
%type <callIndex> call_index index
%type <exp> expr
%type <literal> literal
%type <integer> INT_LITERAL
%type <character> CHAR_LITERAL
%type <boolean> BOOL_LITERAL
%type <variable> variable

%left '&'
%left NOT
%left RELOP
%left '+'
%left '*'

%%

program:
  PROGRAM m2 decls m0 block
  ;

block:
  BEG command_list m0 END { g_symTable.ExitBlock(); }
  ;

decls:
  decls m0 decl ';'
  | /* empty */
  ;

decl:
  var_decl
  | array_def
  | proc_decl
  ;

var_decl:
  TYPE ':' identifier
  {
    $$ = $1;
    g_symTable.AddSymbol( *$3, *$1, "variable", newOffset( *$1 ) );
  }
  | var_decl ',' identifier
  {
    $$ = $1;
    g_symTable.AddSymbol( *$3, *$1, "variable", newOffset( *$1 ) );
  }
  ;

m0:
  /* empty */ { $$ = g_nextline; }
  ;
m1:
  /* empty */
  {
    $$ = new Statement();
    $$->GetNextList().push_back( g_nextline );
    emit( "jump", "__patchable" );
  }
  ;
m2:
  /* empty */ { g_symTable.EnterBlock(); g_numParams = 0; }
  ;

m3:
  /* empty */
  ;

array_def:
  ARRAY identifier m0 RELOP m3 array_def2
  {
    assert( $4 == '=' );
    Symbol symbol = g_symTable.AddSymbol( *$2, g_currentArrayType,
                                          "array",
                                           newOffset( g_currentArrayType, g_currentArrayStart - g_currentArrayEnd ) );
    symbol.SetArrayLimits( g_currentArrayStart, g_currentArrayEnd );
  }
  ;

array_def2:
  '[' limits ']' TYPE { g_currentArrayType = *$4; }
  ;

limits:
  INT_LITERAL ':' INT_LITERAL { g_currentArrayStart = $1; g_currentArrayEnd = $3; }
  ;

proc_decl:
  proc_header proc_body
  ;

proc_header:
  PROCEDURE m0 proc_name m2 proc_params { emit( "goto", "__patchable" ); }
  ;

proc_body:
  ':' decls m0 block
  ;

proc_params:
  '(' param_list ')'
  | /* empty */
  ;

param_list:
  param_list ',' param
  | param
  ;
param:
  mode TYPE ':' identifier
  {
    g_symTable.AddSymbol( *$4, *$2, "parameter", newOffset( *$2 ) );
  }
  ;

mode:
  VALUE
  ;
proc_name:
  identifier { g_symTable.AddSymbol( *$1, "procedure", "procedure", g_nextline ); }
  ;
command_list:
  command
  {
    $$ = new Statement;
    $$->SetNextList( $1-> GetNextList() );
  }
  | command_list ';' m0 command
  {
    $$ = new Statement;
    backpatch( $1->GetNextList(), Literal::ToString( $3 ) );
    $$->SetNextList( $4->GetNextList() );
  }
  ;

command:
  command_assign  { $$ = new Statement; }
  | command_while { $$ = $1; }
  | command_if    { $$ = $1; }
  | command_read  { $$ = new Statement; }
  | command_write { $$ = new Statement; }
  | proc_call     { $$ = new Statement; }
  ;

command_assign:
  variable ASSIGNOP expr
  {
    if( $1->type == "identifier" )
    {
      Symbol& symbol = g_symTable.GetSymbol( $1->identifier );
      assert( symbol.GetType() == $3->GetType() );
      emit( "assign", symbol.GetName(), $3->GetName() );
    }
    else
    {
      Symbol& symbol = g_symTable.GetSymbol( $1->identifier );
      assert( symbol.GetType() == $3->GetType() );
      emit( "assignarray", symbol.GetName(), $1->index, $3->GetName() );
    }
  }
  ;
command_while:
  WHILE m0 expr DO m0 command_list ENDWHILE
  {
    $$ = new Statement();
    assert( $3->GetType() == "boolean" );

    backpatch( $6->GetNextList(), Literal::ToString( $2 ) );
    backpatch( $3->GetTrueList(), Literal::ToString( $5 ) );
    $$->SetNextList( $3->GetFalseList() );
  }
  ;

command_if:
  IF expr THEN m0 command_list ENDIF
  {
    $$ = new Statement();
    assert( $2->GetType() == "boolean" );

    backpatch( $2->GetTrueList(), Literal::ToString( $4 ) );
    $$->SetNextList( $2->GetFalseList(), $5->GetNextList() );
  }
  | IF expr THEN m0 command_list m1 ELSE m0 command_list ENDIF
  {
    $$ = new Statement();
    assert( $2->GetType() == "boolean" );

    backpatch( $2->GetTrueList(), Literal::ToString( $4 ) );
    backpatch( $2->GetFalseList(), Literal::ToString( $8 ) );
    $$->SetNextList( $5->GetNextList(), $6->GetNextList() );
    $$->AppendNextList( $9->GetNextList() );
  }
  ;
command_read:
  READ variable { emit( "read", $2->identifier ); }
  ;
command_write:
  WRITE expr    { emit( "write", $2->GetName() ); }
  ;
variable:
  identifier    { $$ = new Variable(); $$->identifier = *$1; $$->type = "identifier";  }
  | call_index  { $$ = new Variable(); $$->identifier = g_currentCall; $$->index = g_currentCallIndex; $$->type = "call_index"; }
  ;

proc_call:
  identifier    { emit( "call", Literal::ToString( g_symTable.GetSymbol( *$1 ).GetOffset() ) , Literal::ToString( 0 ) ); }
  | call_index
  {
    for( ParamList::const_iterator i = $1->GetParamList().begin(); i != $1->GetParamList().end(); ++i )
      emit( "param", *i );

    emit( "call",
          Literal::ToString( g_symTable.GetSymbol( $1->GetIdentifier() ).GetOffset() ),
          Literal::ToString( $1->GetNumParams() ) );
  }
  ;
call_index:
  index ')' { $$ = $1; }
  ;
index:
  variable2 '(' expr
  {
    std::clog << "Index Type: " << $3->GetType() << std::endl;
    assert( $3->GetType() == "integer" );
    $$ = new CallIndex();
    $$->SetIdentifier( *$1 );
    $$->AddParam( $3->GetName() );
    g_currentCallIndex = $3->GetName();
  }
  | index ',' expr { }
  ;
variable2:
  identifier { g_currentCall = *$1; $$ = $1; }
  ;
expr:
  expr '&' m0 expr
  {
    $$ = new BooleanExpression( *$1, *$4 );
    $$->SetName( newTemp() );

    backpatch( $$->GetTrueList(), Literal::ToString( $3 ) );
    $$->SetTrueList( $1->GetTrueList() );
    $$->SetFalseList( $1->GetFalseList(), $4->GetFalseList() );
  }
  | NOT expr
  {
    $$ = new BooleanExpression( *$2, *$2 );
    $$->SetName( newTemp() );
    $$->SwapLists();
  }
  | expr RELOP expr
  {
    $$ = new BooleanExpression( *$1, *$3 );
    $$->SetName( newTemp() );

    $$->SwapLists();
    $$->GetTrueList().push_back( g_nextline );
    $$->GetFalseList().push_back( g_nextline + 1 );

    std::clog << "Pushing: " << g_nextline << " " << g_nextline + 1 << std::endl;

    emit( relopToOp( std::string( &$2, 1 ) ),
              "__patchable",
              $1->GetName(), $3->GetName() );
    emit( "jump", "__patchable" );
  }
  | expr '+' expr
  {
    $$ = new ArithmeticExpression( *$1, *$3 );
    $$->SetName( newTemp() );
    emit( "add", $$->GetName(), $1->GetName(), $3->GetName() );
  }
  | expr '*' expr
  {
    $$ = new ArithmeticExpression( *$1, *$3 );
    $$->SetName( newTemp() );
    emit( "mul", $$->GetName(), $1->GetName(), $3->GetName() );
  }
  | variable
  {
    $$ = new Expression();
    Symbol& symbol = g_symTable.GetSymbol( $1->identifier );

    if( symbol.GetClass() == "array" )
    {
      $$->SetName( newTemp() );
      $$->SetType( symbol.GetType() );
      emit( "readarray", $$->GetName(), g_currentCall, g_currentCallIndex );
    }
    else
    {
      $$->SetName( symbol.GetName() );
      $$->SetType( symbol.GetType() );
    }
  }
  | literal
  {
    $$ = new Expression();
    $$->SetName( std::string( "#" ) + $1->ToString() );
    $$->SetType( $1->GetType() ); /* TODO: FIX ME */

    if( $$->GetType() == "boolean" )
    {
      if( $1->ToString() == "true" ) $$->GetTrueList().push_back( g_nextline );
      if( $1->ToString() == "false" ) $$->GetFalseList().push_back( g_nextline );
      else std::cerr << "ERROR: Literal Type Error: " << $1->ToString() << std::endl;
      emit( "jump", "__patchable" );
    }
  }
  | '(' expr ')'
  {
    $$ = $2;
  }
  ;
literal:
  INT_LITERAL
  {
    $$ = new Literal( "integer", $1 );
  }
  | CHAR_LITERAL
  {
    $$ = new Literal( "char", $1 );
  }
  | BOOL_LITERAL
  {
    $$ = new Literal( "boolean", $1 );
  }
  ;

identifier:
  IDENTIFIER { g_currentIdentifier = *$$; }
  ;

%%

int main( void )
{
  yyparse();

  for( unsigned int i = 0; i < g_quadArray.size(); ++i ) {
    std::cout << "(" << i  << ") ";
    g_quadArray[i].Emit( std::cout );
  }

  return 0;
}
